package utils.binary.impl;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import utils.binary.ByteSequence;
import utils.binary.BytesSerializable;
import utils.binary.BytesUtils;
import utils.binary.io.BytesInputStream;

/**
 * <h1>字节数据片段</h1>
 *
 * @author Zhou Yifan
 */
public class BytesSlice implements ByteSequence, BytesSerializable {

    public static final BytesSlice EMPTY = new BytesSlice(new byte[0], 0, 0);

    private byte[] bytes;

    private int dataOffset;

    private int size;

    public BytesSlice(byte[] bytes) {
        this(bytes, 0, bytes.length);
    }

    public BytesSlice(byte[] bytes, int offset) {
        this(bytes, offset, bytes.length - offset);
    }

    @Contract(pure = true)
    public BytesSlice(byte @NotNull [] bytes, int offset, int size) {
        if (offset + size > bytes.length) {
            throw new IndexOutOfBoundsException();
        }
        this.bytes = bytes;
        this.dataOffset = offset;
        this.size = size;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public int getSize() {
        return size;
    }

    protected void checkBoundary(int offset, int len) {
        if (offset < dataOffset || offset + len > dataOffset + this.size) {
            throw new IndexOutOfBoundsException(
                    "The accessing index is out of BytesSlice's bounds! --[offset="
                            + offset + ", len=" + len + "]"
            );
        }
    }

    /* get */

    /**
     * 返回首个字节
     *
     * @return byte
     */
    public byte getByte() {
        return getByte(0);
    }

    /**
     * 返回首个字节
     *
     * @param offset offset
     * @return byte
     */
    public byte getByte(int offset) {
        int off = this.dataOffset + offset;
        checkBoundary(off, 1);
        return bytes[off];
    }

    /**
     * 读取 1 个字节，转换为 char 类型的数值返回
     *
     * @return char
     */
    public char getChar() {
        return getChar(0);
    }

    /**
     * 从指定的偏移量开始，读取 1 个字节，转换为 char 类型的数值返回
     *
     * @param offset offset
     * @return char
     */
    public char getChar(int offset) {
        int off = this.dataOffset + offset;
        checkBoundary(off, 2);
        return BytesUtils.toChar(bytes, off);
    }

    /**
     * 连续读取 2 个字节，转换为 short 类型的数值返回
     *
     * @return short
     */
    public short getShort() {
        return getShort(0);
    }

    /**
     * 从指定的偏移量开始，读取连续 2 个字节，转换为 short 类型的数值返回
     *
     * @param offset offset
     * @return short
     */
    public short getShort(int offset) {
        int off = this.dataOffset + offset;
        checkBoundary(off, 2);
        return BytesUtils.toShort(bytes, off);
    }

    /**
     * 读取连续 4 个字节，转换为 int 类型的数值返回
     *
     * @return int
     */
    public int getInt() {
        return getInt(0);
    }

    /**
     * 从指定的偏移量开始，读取连续 4 个字节，转换为 int 类型的数值返回
     *
     * @param offset offset
     * @return int
     */
    public int getInt(int offset) {
        int off = this.dataOffset + offset;
        checkBoundary(off, 4);
        return BytesUtils.toInt(bytes, off);
    }

    /**
     * 读取连续 8 个字节，转换为 long 类型的数值返回
     *
     * @return long
     */
    public long getLong() {
        return getLong(0);
    }

    /**
     * 从指定的偏移量开始，读取连续 8 个字节，转换为 long 类型的数值返回
     *
     * @param offset offset
     * @return long
     */
    public long getLong(int offset) {
        int off = this.dataOffset + offset;
        checkBoundary(off, 8);
        return BytesUtils.toLong(bytes, off);
    }

    /**
     * 从 {@link #dataOffset} 开始，将 {@link #bytes} 大小为 {@link #size} 的字节码转换为字符串
     *
     * @return long
     */
    public String getString() {
        return BytesUtils.toString(bytes, dataOffset, size);
    }

    /**
     * 读取字节片，转化为 BytesInputStream
     *
     * @return {@link BytesInputStream}
     */
    public BytesInputStream getInputStream() {
        return getInputStream(0);
    }

    /**
     * 从指定位置开始读取字节片，转化为 BytesInputStream
     *
     * @param offset 指定位置
     * @return {@link BytesInputStream}
     */
    public BytesInputStream getInputStream(int offset) {
        int off = this.dataOffset + offset;
        int s = size;
        checkBoundary(off, s);
        return new BytesInputStream(bytes, off, s);
    }

    /**
     * 获取字节片副本
     *
     * @return byte[]
     */
    public byte[] getBytesCopy() {
        if (size == 0) {
            return BytesUtils.EMPTY_BYTES;
        }
        byte[] copy = new byte[size];
        System.arraycopy(bytes, dataOffset, copy, 0, size);
        return copy;
    }

    /**
     * 从指定位置开始获取指定长度的字节片副本
     *
     * @param offset 指定位置
     * @param size   指定长度
     * @return byte[]
     */
    public byte[] getBytesCopy(int offset, int size) {
        int newOffset = dataOffset + offset;
        checkBoundary(newOffset, size);

        if (size == 0) {
            return BytesUtils.EMPTY_BYTES;
        }
        byte[] copy = new byte[size];
        System.arraycopy(bytes, newOffset, copy, 0, size);
        return copy;
    }

    /**
     * 从指定位置开始，获取当前字节片的指定大小的子字节片
     *
     * @param offset 指定位置
     * @param size   指定长度
     * @return {@link BytesSlice}
     */
    public BytesSlice getSlice(int offset, int size) {
        int newOffset = dataOffset + offset;
        checkBoundary(newOffset, size);
        return new BytesSlice(bytes, newOffset, size);
    }

    /**
     * 从当前数据片段复制数据到指定的缓冲数组；
     *
     * @param srcOffset  要复制的数据片段的起点位置；
     * @param dest       接收数据的缓冲数组；
     * @param destOffset 缓冲数组保存数据的起始位置；
     * @param size       要复制的字节大小；如果指定值超过当前数据片段的大小，在复制并返回实际复制的字节数；
     * @return 已复制的字节大小；
     */
    public int copy(int srcOffset, byte[] dest, int destOffset, int size) {
        if (srcOffset < 0 || srcOffset >= this.size) {
            throw new IndexOutOfBoundsException(
                    "The argument srcOffset is out of BytesSlice's bounds! --[srcOffset="
                            + srcOffset + "]"
            );
        }
        if (size < 0) {
            throw new IllegalArgumentException(
                    "The size argument is negative! --[size="
                            + size + "]"
            );
        }

        int newOffset = dataOffset + srcOffset;
        int count = this.size - srcOffset;
        count = Math.min(count, size);
        System.arraycopy(bytes, newOffset, dest, destOffset, count);

        return count;
    }

    /**
     * 字节序列的大小
     *
     * @return int 值
     */
    @Override
    public int size() {
        return size;
    }

    /**
     * 获取指定位置的字节
     *
     * @param index 指定位置，int 值
     * @return 字节， byte 值
     */
    @Override
    public byte byteAt(int index) {
        return getByte(index);
    }

    /**
     * 获取字节序列的子序列
     *
     * @param start 开始位置，int 值，包含
     * @param end   结束位置，int 值，不包含
     * @return 字节序列 {@link ByteSequence}
     */
    @Override
    public ByteSequence subSequence(int start, int end) {
        return getSlice(start, end - start);
    }

    /**
     * 从指定位置开始，将当前字节序列复制到目标字节数组的指定位置
     *
     * @param srcOffset    当前字节数组开始复制的位置
     * @param target       目标字节序列
     * @param targetOffset 目标字节数组开始位置
     * @param length       复制长度
     * @return int 值，表示复制的字节长度
     */
    @Override
    public int copyTo(int srcOffset, byte[] target, int targetOffset, int length) {
        return copy(srcOffset, target, targetOffset, length);
    }

    /**
     * 以字节数组形式获取字节块的副本
     *
     * @return byte[]
     */
    @Override
    public byte[] toBytes() {
        return getBytesCopy();
    }

    @Override
    public boolean equalBytes(byte @NotNull [] data) {
        if (data.length != size()) {
            return false;
        }
        for (int i = 0; i < data.length; i++) {
            if (data[i] != bytes[dataOffset + i]) {
                return false;
            }
        }
        return true;
    }
}
